/**********************************************************************************
// DirectDrawGame Source
//
// Creation:	19 May 2007
// Updated:		30 Set 2009
// Compiler:	Visual Studio 2008
//
// Notes:		A DirectDrawGame is a WindowsGame that makes use of DirectDraw
//				functions to access the video card aceleration hardware. 
**********************************************************************************/

#include "DirectDrawGame.h"

/**********************************************************************************/

DirectDrawGame::DirectDrawGame() 
{
	coopLevel		= DDSCL_FULLSCREEN |				// set defatul cooperation level to fullscreen 
		              DDSCL_EXCLUSIVE | 
					  DDSCL_ALLOWREBOOT; 
	fpsMode         = FIXED30FPS;						// default to 30 FPS
	refreshRate		= 0;								// uses default refresh rate (60Hz)
	screenBpp		= 32;								// set color depth to 32 bbp
	buffering		= DOUBLEBUFFERING;					// set buffering mode to DOUBLEBUFFERING				

	// the blitter is used to clean the drawing surface
	// before each new frame

	// initialize the DDBLTFX structure for the blitter
	ZeroMemory(&ddBltFx, sizeof(ddBltFx));
	ddBltFx.dwSize = sizeof(ddBltFx);
	
	// set the fill color in the DDBLTFX structure
	// this will be the default window background color

	ddBltFx.dwFillColor = Color().RGB32bits();			// set background color to black

	lpDD		 = NULL;								// direct draw object
	lpDDSPrimary = NULL;								// pointer to primary surface
	lpDDSBack	 = NULL;								// pointer to back buffer
	lpDDSDisplay = NULL;								// pointer to the primary or back buffer
}

/**********************************************************************************/

DirectDrawGame::~DirectDrawGame()
{

	// release the secondary (back buffer) surface 
	if (lpDDSBack)
	{
		lpDDSBack->Release();
		lpDDSBack = NULL;
	}

	// release the primary surface
	if (lpDDSPrimary)
	{
		lpDDSPrimary->Release();
		lpDDSPrimary = NULL;
	}

	// release the IDirecDraw7 interface
	if (lpDD)
	{
		lpDD->Release();
		lpDD = NULL;
	}
}

/**********************************************************************************/

void DirectDrawGame::SetWindowMode(int mode)
{
	// valid screen modes are WINDOWED and FULLSCREEN
	
	// window mode needs to be set at the window level 
	WindowsGame::SetWindowMode(mode);

	// window mode needs to be set at the directdraw level too
	if (mode)
		// windowed mode
		coopLevel = DDSCL_NORMAL; 
	else
		//fullscreen
		coopLevel = DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT;
}

/**********************************************************************************/

void DirectDrawGame::ClearScreen()
{
	// clear the video Memory for the next frame
	// use the blitter to clean the screen (the screen must be unlocked)
	lpDDSDisplay->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx);
}

/**********************************************************************************/

void DirectDrawGame::PageFlip()
{
	if (buffering > SINGLEBUFFERING)
	{
		// perform the page flip
		while (FAILED(lpDDSPrimary->Flip(NULL, DDFLIP_WAIT)));
	}
}

/**********************************************************************************/

void DirectDrawGame::LockScreen()
{
	// initialize the DDSURFACEDESC2 structure 
	ZeroMemory(&ddSD, sizeof(ddSD));
	ddSD.dwSize = sizeof(ddSD);

	// lock the screen surface (primary or back buffer)
	lpDDSDisplay->Lock(NULL, &ddSD, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);
		
	// set the video memory pointer and the memory pitch

	// 32 bits per pixel screen format
	// -------------------------------
	// The video memory is divided in blocks of 2 bytes (USHORT)
	// lPitch is given in (pixel width * 2 bytes per pixel), i.e
	// it must be divided by 2 to obtain the correct pixel width 
	// of each line of video memory. 
	memoryPitch = ddSD.lPitch >> 2;
	videoRAM = (UINT *) ddSD.lpSurface;
}

/**********************************************************************************/

void DirectDrawGame::UnLockScreen()
{
	// unlock the display surface (primary or back buffer)
	lpDDSDisplay->Unlock(NULL);
}

/**********************************************************************************/

int DirectDrawGame::Text(char *text, int x, int y, COLORREF color)
{
	// this function draws the sent text on screen using the given color

	// working graphics device context
	HDC xdc;

	// get the graphics device context
	if (FAILED(lpDDSDisplay->GetDC(&xdc)))
		return 0;

	// set the colors for the text
	SetTextColor(xdc, color);

	// set text background mode to transparent so black isn't copied
	SetBkMode(xdc, TRANSPARENT);

	// draw the text
	TextOut(xdc, x, y, text, int(strlen(text)));

	// release the dc
	lpDDSDisplay->ReleaseDC(xdc);
	
	// return success
	return 1;
}

/**********************************************************************************/

bool DirectDrawGame::Initialize(HINSTANCE appInstance)
{
	WindowsGame::Initialize(appInstance);

	// create IDirectDraw Interface 7.0 object
	if FAILED(DirectDrawCreateEx(NULL, (void **)&lpDD, IID_IDirectDraw7, NULL))
		return false;

	// set cooperation level (full screen or windowed mode)
	if FAILED(lpDD->SetCooperativeLevel(WindowsGame::GetWindowHandle(), coopLevel))
		return false;

	// set display mode 
	if (GetWindowMode() == FULLSCREEN)
		if FAILED(lpDD->SetDisplayMode(GetWindowWidth(), GetWindowHeight(), screenBpp, refreshRate, NULL))
			return false;
	
	// windowed mode can not use double or triple buffering
	if (GetWindowMode() == WINDOWED)
		buffering = SINGLEBUFFERING;

	// clear the ddSD structure, 
	// set structure size and flags
	ZeroMemory(&ddSD, sizeof(ddSD));					// MS recommends clearing out the structure
	ddSD.dwSize = sizeof(ddSD);							// fill in the size of the structure

	// set ddSD structure with the 
	// corresponding buffering technique
	switch (buffering)
	{

	case SINGLEBUFFERING:
		ddSD.dwFlags		   = DDSD_CAPS;					// indicates that DDD_CAPS is a valid member
		ddSD.ddsCaps.dwCaps    = DDSCAPS_PRIMARYSURFACE;	// this surface is the primary surface
		break;

	case DOUBLEBUFFERING:
		ddSD.dwFlags           = DDSD_CAPS |				// indicates that DDD_CAPS is a valid member
			                     DDSD_BACKBUFFERCOUNT;		// enable backbuffers
		ddSD.dwBackBufferCount = 1;							// set the number of backbuffers
		ddSD.ddsCaps.dwCaps    = DDSCAPS_PRIMARYSURFACE |	// this surface is the primary surface
		                         DDSCAPS_COMPLEX |			// request a complex and 
						         DDSCAPS_FLIP;				// flipable surface
		break;

	case TRIPLEBUFFERING:
		ddSD.dwFlags           = DDSD_CAPS |				// indicates that DDD_CAPS is a valid member
			                     DDSD_BACKBUFFERCOUNT;		// enable backbuffers
		ddSD.dwBackBufferCount = 2;							// set the number of backbuffers
		ddSD.ddsCaps.dwCaps    = DDSCAPS_PRIMARYSURFACE |	// this surface is the primary surface
		                         DDSCAPS_COMPLEX |			// request a complex and 
						         DDSCAPS_FLIP;				// flipable surface
		break;
	}

	// create the primary surface
	if FAILED(lpDD->CreateSurface(&ddSD, &lpDDSPrimary, NULL))
		return false;

	// if user asked for DOUBLEBUFFERING or TRIPLEBUFFERING
	// attach the additional surface to the primary one

	if (buffering > SINGLEBUFFERING)
	{
		// now query for attached surface from the primary surface
		ddSD.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
		// get attached back buffer surface
		lpDDSPrimary->GetAttachedSurface(&ddSD.ddsCaps, &lpDDSBack);
		// the display surface is the back buffer
		lpDDSDisplay = lpDDSBack;
	}
	else
	{
		// the display surface is the primary surface
		lpDDSDisplay = lpDDSPrimary;
	}

	// pointer to DirectDraw clipper
	LPDIRECTDRAWCLIPPER lpDDClipper;	
	
	// first create the DirectDraw clipper
	if (FAILED(lpDD->CreateClipper(0, &lpDDClipper, NULL)))
		return NULL;

	// attach the clipper to the application's window
	if (FAILED(lpDDClipper->SetHWnd(0, GetWindowHandle())))
		return false;
	
	// now associate the clipper whit the surface
	if (FAILED(lpDDSDisplay->SetClipper(lpDDClipper)))
		return false;

	// release clipper to reduce the reference count
	lpDDClipper->Release();
	
	///////////////////////////////

	// return success
	return true;
}
/**********************************************************************************/

int DirectDrawGame::Start() 
{
	MSG msg;					// generic windows message
	LARGE_INTEGER startTime;	// time before current frame
	LARGE_INTEGER endTime;		// time after current frame
	LARGE_INTEGER freqTime;		// machine CPU frequency (cycles/second)
	float frameTime;			// time elapsed for the current frame in seconds 
	
	// get machine frequency (cycles/second)
	QueryPerformanceFrequency(&freqTime);

	// initialize game here
	GameInit();

	// enter main game loop
	while(true)
	{
		// test if there is a message in queue, if so get it
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
	    { 
			// test if this is a quit
			if (msg.message == WM_QUIT)
				break;
	
			// translate any accelerator keys
			TranslateMessage(&msg);

			// send the message to the window proc
			DispatchMessage(&msg);
		} // end if


		/**************************************/
		/*             Game Main              */
		/**************************************/

		// get start frame time
		QueryPerformanceCounter(&startTime);

		// game logic processing goes here
		GameLogic();
	
		// clear the screen for next frame
		ClearScreen();

		// game drawing goes here
		GameDraw();

		// get end frame time
		QueryPerformanceCounter(&endTime);

		// calculate elapsed frame time in seconds
		frameTime = (endTime.QuadPart - startTime.QuadPart) / float(freqTime.QuadPart);
		char buffer[80];
		sprintf_s(buffer, "Frame Time: %f", frameTime);
		Text(buffer, GetWindowWidth()-170, 10, RGB(255,255,255));
		sprintf_s(buffer, "Sleep Time: %d", DWORD((0.0333 - frameTime)*1000));
		Text(buffer, GetWindowWidth()-170, 30, RGB(255,255,255));

		// flip primary and back buffers
		PageFlip();

		/**************************************/
		// check FPS mode to use
		if (fpsMode == FIXED30FPS)
			// lock frame updates to 30 FPS
			if (frameTime < 0.0333) 
				Sleep(DWORD((0.0333 - frameTime)*1000));
		/**************************************/
       
	} // end while

	// closedown game here
	GameShutdown();

	// return to Windows
	return int(msg.wParam);
}

/**********************************************************************************/
